package com.travel.recommend.strategyPattern;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.travel.common.attraction.AttractionTagMap;
import com.travel.common.to.AttractionInfoTo;
import com.travel.recommend.entity.*;
import com.travel.recommend.feign.SightFeignService;
import com.travel.recommend.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 基于景点标签推荐
 */
@Slf4j
@Service
public class TagRecommendationStrategy implements RecommendationStrategy{
//    @Autowired
//    InitialLikeTagService initialLikeTagService;
//    @Autowired
//    SightFeignService sightFeignService;
//    @Autowired
//    UserTagWeightService userTagWeightService;
//    @Autowired
//    TagPopularityService tagPopularityService;
//    @Autowired
//    UserAttractionWeightService userAttractionWeightService;
//    @Autowired
//    RecommendParameterService recommendParameterService;
//    @Autowired
//    SimilarityConfigService similarityConfigService;
    private final InitialLikeTagService initialLikeTagService;

    private final SightFeignService sightFeignService;

    private final UserTagWeightService userTagWeightService;

    private final TagPopularityService tagPopularityService;

    private final UserAttractionWeightService userAttractionWeightService;

    private final RecommendParameterService recommendParameterService;

    private final SimilarityConfigService similarityConfigService;

    public TagRecommendationStrategy(InitialLikeTagService initialLikeTagService,
                                     SightFeignService sightFeignService,
                                     UserTagWeightService userTagWeightService,
                                     TagPopularityService tagPopularityService,
                                     UserAttractionWeightService userAttractionWeightService,
                                     RecommendParameterService recommendParameterService,
                                     SimilarityConfigService similarityConfigService) {
        this.initialLikeTagService = initialLikeTagService;
        this.sightFeignService = sightFeignService;
        this.userTagWeightService = userTagWeightService;
        this.tagPopularityService = tagPopularityService;
        this.userAttractionWeightService = userAttractionWeightService;
        this.recommendParameterService = recommendParameterService;
        this.similarityConfigService = similarityConfigService;
    }

    static double tagPopularityWeight; //计算景点相似度中，景点标签权重
    static double likeCollectionCommentWeight; //计算景点相似度中，点赞/收藏/评论权重
    static double clicksWeight; //计算景点相似度中，被点击次数权重

    static int policyDivision; //策略划分值(用户行为数)，<基于标签，>=协同过滤
    static int similarityAttractionNum; //附加几个相似度高的景点
    static int neighboringNum; //基于用户行为，寻找几个最邻近用户


    @Override
    public List<Integer> recommend(Integer userId) {
        // 最终要返回的结果：推荐景点的id集合
        List<Integer> recommendAttractionId = new ArrayList<>();

        //1. 获取用户的初始喜好标签（不为null）
        InitialLikeTagEntity entity = initialLikeTagService.getOne(new QueryWrapper<InitialLikeTagEntity>().eq("user_id", userId));
        List<String> initialTag = Arrays.asList(entity.getInitialTag().split(","));
        System.out.println("初始喜好标签===" + initialTag);

        Integer behaviorNum = sightFeignService.getBehaviorNum(userId);
//        Integer behaviorNum = 0;
        System.out.println("用户的行为次数总和==" + behaviorNum);
        //2. 用户无行为 -> 按初始喜好标签的流行度降序推荐  计算公式：该标签下所有景点的score总和，score总和/最大score总和 × 100
        if (behaviorNum == 0) {
            //2.1 获取所有标签的流行度
            // 获取所有标签的流行度，并按照流行度排序
            List<TagPopularityEntity> sortedTags = tagPopularityService.list();
            sortedTags.sort(Comparator.comparingDouble(TagPopularityEntity::getPopularity).reversed());

            // 取出sortedTags中出现在初始喜好标签的的标签，并按流行度降序排序
            List<TagPopularityEntity> filteredTags = new ArrayList<>();

            for (TagPopularityEntity tagPopularityEntity : sortedTags) {
                if (initialTag.contains(tagPopularityEntity.getTag())) {
                    filteredTags.add(tagPopularityEntity);
                }
            }
            filteredTags.sort(Comparator.comparingDouble(TagPopularityEntity::getPopularity).reversed());

            // 取出sortedTags中初始喜好标签没有出现的标签，并按流行度降序排序
            for (TagPopularityEntity tagPopularityEntity : sortedTags) {
                if (!initialTag.contains(tagPopularityEntity.getTag())) {
                    filteredTags.add(tagPopularityEntity);
                }
            }

            // 输出排序后的结果
            filteredTags.forEach(tagPopularityEntity -> {
                System.out.println(tagPopularityEntity.getTag() + " - " + tagPopularityEntity.getPopularity());
            });

            //2.2 遍历标签降序的流行度 取出该标签下所有的景点 按评分降序
            for (TagPopularityEntity tag : filteredTags) {
                String tagName = tag.getTag();
                System.out.println("景点标签=="+tagName);
                // 远程调用travel-sight微服务获取景点信息
                List<AttractionInfoTo> attractionListByTagName = sightFeignService.getAttractionByTagName(tagName);
                // 评分降序
                attractionListByTagName.sort(Comparator.comparingDouble(AttractionInfoTo::getScore).reversed());

                attractionListByTagName.forEach(attraction -> System.out.println(attraction.getAttractionId() + " - "+attraction.getName()+" - "+attraction.getScore()));

                List<Integer> attractionIdList = attractionListByTagName.stream().map(AttractionInfoTo::getAttractionId).collect(Collectors.toList());
                recommendAttractionId.addAll(attractionIdList);
            }
            System.out.println("推荐的景点id集合==="+recommendAttractionId);
            return recommendAttractionId;
        }

        //3. 用户有行为 -> 按照用户对有行为的标签的权重（累加同标签的景点weight）
        //3.1 获取该用户对有行为的标签的权重，降序排序，
        List<UserTagWeightEntity> tagWeights = userTagWeightService.list(new QueryWrapper<UserTagWeightEntity>().eq("user_id", userId));
        tagWeights.sort(Comparator.comparingDouble(UserTagWeightEntity::getTagWeight).reversed());

        tagWeights.forEach(System.out::println);
        //3.1.1 获取对应标签的所有景点，景点按评分降序（ams_attraction_info表中一个字段 评分计算公式：(点击数/最大点击 × 0.1 + 点赞数/最大点赞数 × 0.3 + 收藏数/最大收藏数 × 0.3 + 评论数/最大评论数 ×0.3 ) ×100）
        tagWeights.forEach(tag ->{
            String tagName = tag.getTagName();
            System.out.println("景点标签==="+tagName);
            List<AttractionInfoTo> attractionByTagName = sightFeignService.getAttractionByTagName(tagName);
            attractionByTagName.sort(Comparator.comparingDouble(AttractionInfoTo::getScore).reversed());

            attractionByTagName.forEach(attraction ->
                    System.out.println(attraction.getAttractionId()+" - "+attraction.getName()+" - "+attraction.getScore()));

            List<Integer> collect = attractionByTagName.stream().map(AttractionInfoTo::getAttractionId).collect(Collectors.toList());
            recommendAttractionId.addAll(collect);

        });

        //3.2 结合用户初始喜好标签
        //3.2.1 无行为的初始喜好标签，标签流行度降序
        // 有行为的标签
        List<String> tagList = tagWeights.stream().map(UserTagWeightEntity::getTagName).collect(Collectors.toList());
        // 获取 初始喜好标签中有 但有行为标签中无 的景点标签
        List<String> collect = initialTag.stream().filter(tag -> !tagList.contains(tag)).collect(Collectors.toList());

        //3.2.2 获取对应标签的所有景点，景点按评分降序
        System.out.println(collect);
        collect.forEach(tag -> {
            List<AttractionInfoTo> attractionByTagName = sightFeignService.getAttractionByTagName(tag);
            attractionByTagName.sort(Comparator.comparingDouble(AttractionInfoTo::getScore).reversed());

            attractionByTagName.forEach(attraction ->
                    System.out.println(attraction.getAttractionId()+" - "+attraction.getName()+" - "+attraction.getScore()));

            List<Integer> collect1 = attractionByTagName.stream().map(AttractionInfoTo::getAttractionId).collect(Collectors.toList());
            recommendAttractionId.addAll(collect1);

        });

        //3.3 附加与用户有行为高度相似的n个景点（过滤重复的，取前n个）
        // 有行为的标签 和 初始喜好的标签 并集 即：附加的相似度高的景点不能在这些标签中
        List<String> collect1 = Stream.concat(initialTag.stream(), tagList.stream()).distinct().collect(Collectors.toList());

        // 获取所有的景点标签
        List<String> allTags = AttractionTagMap.getAllTags();
        // 过滤掉collect1 即：附加相似度高的景点就从这些景点标签中取寻找
        List<String> addSimilarAttractionTags = allTags.stream().filter(tag -> !collect1.contains(tag)).collect(Collectors.toList());
        System.out.println("addSimilarAttractionTags==="+addSimilarAttractionTags);
        // 如果为空 就不用附加景点了
        if(addSimilarAttractionTags.size() != 0){
            List<Integer> minDistances = SimilarAttraction(addSimilarAttractionTags,userId);
            recommendAttractionId.addAll(minDistances);
        }

        return recommendAttractionId;
    }

    /**
     * 获取与该用户有行为的景点中 相似度最高的其它景点（不在以要推荐的景点标签中的 - 挖掘用户新兴趣）
     */
    public List<Integer> SimilarAttraction(List<String> addSimilarAttractionTags,Integer userId){
        //1. 获取该用户有行为的所有景点
        List<UserAttractionWeightEntity> entities = userAttractionWeightService.list(new QueryWrapper<UserAttractionWeightEntity>().eq("user_id", userId));
        List<Integer> ids = entities.stream().map(UserAttractionWeightEntity::getAttractionId).collect(Collectors.toList());
        List<AttractionInfoTo> list1 = sightFeignService.getAttractionsByIds(ids);
        System.out.println("该用户有行为的景点=================");
        list1.forEach(attraction -> System.out.println(attraction.getName()));

        //2. 获取addSimilarAttractionTags中的所有景点
        List<AttractionInfoTo> list2= new ArrayList<>();
        addSimilarAttractionTags.forEach(tag -> {
            List<AttractionInfoTo> tos = sightFeignService.getAttractionByTagName(tag);
            list2.addAll(tos);
        });
        System.out.println("用户无行为的景点标签，从中寻找相似景点，挖掘用户兴趣=================");
        list2.forEach(attraction -> System.out.println(attraction.getName()));

        // 欧式距离度结果集 景点id - <景点id - 欧式距离>
        Map<Integer, Map<Integer,Double>> similar = new HashMap<>();

        list1.forEach(attraction1 ->{
            Map<Integer,Double> map = new HashMap<>();
            list2.forEach(attraction2 -> {
                Double num = cosineSimilarity(attraction1, attraction2);
                map.put(attraction2.getAttractionId(),num);
            });
            similar.put(attraction1.getAttractionId(),map);
        });

        // 1.entrySet方法获取外层Map similar 的键值对集合
        // 2.flatMap方法将内层Map的键值对转换为一个新的Stream
        similar.entrySet().stream().flatMap(entry -> entry.getValue().entrySet().stream())
                .forEach(innerEntry -> System.out.println(innerEntry.getKey() + " : " + innerEntry.getValue()));


        // 取similarityAttractionNum个欧式距离最小的
        List<Integer> minDistances = similar.entrySet().stream()
                .flatMap(entry -> entry.getValue().entrySet().stream())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (v1, v2) -> Math.min(v1, v2)  // 如果出现重复的key，选择value较小的
                ))
                .entrySet().stream()
                .sorted(Map.Entry.comparingByValue())
                .limit(5)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());

        System.out.println("minDistances "+minDistances);

        return minDistances;
    }

    /**
     * 计算两个景点的欧式距离
     *
     * @return
     */
    public Double cosineSimilarity(AttractionInfoTo to1,AttractionInfoTo to2){
        Double score1 = to1.getScore();
        Integer clickNum1 = to1.getClickNum();
        Integer likeNum1 = to1.getLikeNum();
        Integer collectionNum1 = to1.getCollectionNum();
        Integer commentNum1 = to1.getCommentNum();
        TagPopularityEntity tag1 = tagPopularityService.getOne(new QueryWrapper<TagPopularityEntity>().eq("tag", to1.getTag()));
        Double popularity1 = tag1.getPopularity();
        String location1 = to1.getLocation();

        Double score2 = to2.getScore();
        Integer clickNum2 = to2.getClickNum();
        Integer likeNum2 = to2.getLikeNum();
        Integer collectionNum2 = to2.getCollectionNum();
        Integer commentNum2 = to2.getCommentNum();
        TagPopularityEntity tag2 = tagPopularityService.getOne(new QueryWrapper<TagPopularityEntity>().eq("tag", to2.getTag()));
        Double popularity2 = tag2.getPopularity();
        String location2 = to2.getLocation();

        // 加权处理
        double weightedClickNum1 = clickNum1 * clicksWeight;
        double weightedLikeNum1 = likeNum1 * likeCollectionCommentWeight;
        double weightedCollectionNum1 = collectionNum1 * likeCollectionCommentWeight;
        double weightedCommentNum1 = commentNum1 * likeCollectionCommentWeight;
        double weightedPopularity1 = popularity1 * tagPopularityWeight;

        double weightedClickNum2 = clickNum2 * clicksWeight;
        double weightedLikeNum2 = likeNum2 * likeCollectionCommentWeight;
        double weightedCollectionNum2 = collectionNum2 * likeCollectionCommentWeight;
        double weightedCommentNum2 = commentNum2 * likeCollectionCommentWeight;
        double weightedPopularity2 = popularity2 * tagPopularityWeight;

        // 计算欧式距离
        double distance = Math.sqrt(Math.pow(weightedClickNum1 - weightedClickNum2, 2) + Math.pow(weightedLikeNum1 - weightedLikeNum2, 2)
                + Math.pow(weightedCollectionNum1 - weightedCollectionNum2, 2) + Math.pow(weightedCommentNum1 - weightedCommentNum2, 2)
                + Math.pow(weightedPopularity1 - weightedPopularity2, 2) + Math.pow(score1 - score2, 2));

        // 如果地区相同 距离除以10
        if(location2.equals(location1)){
            distance /= 10;
        }

        log.info(to1.getName() + " " + to2.getName() + " 距离 " + distance);

        return distance;
    }

    /**
     * 加载推荐算法的配置信息
     */
    @Override
    public void initAlgorithmConfig() {
        RecommendParameterEntity recommendParameterEntity = recommendParameterService.getNowRecommendParameter();
        SimilarityConfigEntity similarityConfigEntity = similarityConfigService.getNowSimilarityConfig();

        tagPopularityWeight = similarityConfigEntity.getTagPopularityWeight(); //计算景点相似度中，景点标签权重
        likeCollectionCommentWeight = similarityConfigEntity.getLikeCollectionCommentWeight(); //计算景点相似度中，点赞/收藏/评论权重
        clicksWeight = similarityConfigEntity.getClicksWeight(); //计算景点相似度中，被点击次数权重

        similarityAttractionNum = recommendParameterEntity.getSimilarityAttractionNum(); //基于标签，附加几个相似度高的景点
        neighboringNum = recommendParameterEntity.getNeighboringNum(); //基于用户行为，寻找几个最邻近用户
        policyDivision = recommendParameterEntity.getPolicyDivision(); //策略划分值(用户行为数)，<基于标签，>=协同过滤

    }
}
